package mx.com.crowdgine.util.math;

/**
* A 3-dimensional vector.
* @author Oriam De Gyves
*/
public class Vector3f {
	/**
	 *  A vector whose components are all zero.
	 */
	public static final Vector3f ZERO = new Vector3f(0.0f, 0.0f, 0.0f);
	
	private float x = 0;
    private float y = 0;
    private float z = 0;

    /** Creates a new vector initialized with zeroes.*/
    public Vector3f(){
        x = 0;
        y = 0;
        z = 0;
    }

    /**
     * Creates a new vector initialized with the parameters values.
     * @param x The value to set the x component.
     * @param y The value to set the y component.
     * @param z The value to set the z component.
     */
    public Vector3f(float x, float y, float z){
        this.x = x;
        this.y = y;
        this.z = z;
    }

    /**
     * Creates a new vector initialized with an array.
     * @param array A size-3 array to be mapped with the x, y and z values.
     * @throws RuntimeException If the length of the array is different from 3.
     */
    public Vector3f(float[] array){
        if(array.length != 3)
            throw new RuntimeException("Vector3f constructor: Only 3 element arrays are accepted");

        x = array[0];
        y = array[1];
        z = array[2];
    }

    /**
     * Creates an array representation of the vector.
     * @return Returns a float array of size 3 with the
     * elements of the vector.
     */
    public float[] array(){
        return new float[]{x,y,z};
    }

    /**
     * Adds a vector to the current vector. Doesn't change the vector's values.
     * @param other The vector that will be added.
     * @return A new vector which contains the result of the addition.
     * @see #plus(Vector3f)
     */
    public Vector3f add(Vector3f other){
        return new Vector3f(x + other.getX(),
                            y + other.getY(),
                            z + other.getZ());
    }

    /**
     * Adds a vector to the current vector. Changes the vector's values.
     * @param other The vector that will be added.
     * @see #add(Vector3f)
     */
    public void plus(Vector3f other){
        this.x += other.getX();
        this.y += other.getY();
        this.z += other.getZ();
    }

    /**
     * Subtracts a vector to the current vector. Doesn't change the vector's values.
     * @param other The vector that will be subtracted.
     * @return A new vector which contains the result of the subtraction.
     * @see #minus(Vector3f)
     */
    public Vector3f sub(Vector3f other){
        return new Vector3f(x - other.getX(),
                            y - other.getY(),
                            z - other.getZ());
    }

    /**
     * Subtracts a vector to the current vector. Changes the vector's values.
     * @param other The vector that will be subtracted.
     * @see #sub(Vector3f)
     */
    public void minus(Vector3f other){
        this.x -= other.getX();
        this.y -= other.getY();
        this.z -= other.getZ();
    }

    /**
     * Negates the vector's values. Doesn't change the vector's values.
     * @return A new vector which contains the negation of the current vector's values.
     * @see #negate()
     */
    public Vector3f neg(){
        return new Vector3f(-x, -y, -z);
    }

    /**
     * Negates the vector's values. Changes the current vector's values.
     * @see #neg()
     */
    public void negate(){
        x *= -1;
        y *= -1;
        z *= -1;
    }

    /**
     * Multiplies the vector with a scalar value. Doesn't change the vector's values.
     * @param scalar Scalar value to be multiplied.
     * @return A new vector with the result of the multiplication.
     * @see #scale(float)
     */
    public Vector3f mul(float scalar){
		return new Vector3f(scalar*x, scalar*y, scalar*z);
	}
    
    /**
     * Multiplies the vector with a scalar value. Changes the vector's values.
     * @param scalar Scalar value to be multiplied.
     * @see #mul(float)
     */
    public void scale(float scalar){
    	x *= scalar;
    	y *= scalar;
    	z *= scalar;
    }

    /**
     * Performs a dot operation with another vector.
     * @param other The vector to perform the dot operation with.
     * @return A float value with result of the dot operation.
     */
    public float dot(Vector3f other){
    	return 	x * other.getX() +
				y * other.getY() +
				z * other.getZ();
	}

    /**
     * Performs a cross operation with another vector.
     * @param other The vector to perform the cross operation with.
     * @return A new vector with the result of the cross operation.
     */
    public Vector3f cross(Vector3f other){
    	return new Vector3f(y*other.getZ() - z*other.getY(),
    						x*other.getZ() - z*other.getX(),
    						x*other.getY() - y*other.getX());
    }
    
    /**
     * Calculates the length of the vector.
     * @return A float value with the length of the vector.
     */
    public float length(){
    	float len = lengthSqr();
    	if(len == 0.0f)
    		return 0.0f;
    	else
    		return (float)Math.sqrt(len);
    }
    
    /**
     * 
     * @return
     */
    public float lengthSqr(){
    	return this.dot(this);
    }

    /**
     * Normalizes the vector. Doesn't change the vector's values.
     * @return A new vector with the normalized values.
     * @see #normalize()
     */
    public Vector3f norm(){
		return this.mul(1.0f/length());
	}
    
    /**
     * Normalizes the vector. Changes the vector's values.
     * @see #norm()
     */
    public void normalize(){
    	float len = this.length();
    	if(len != 0){
    		len = 1.0f/len;
    		x *= len;
    		y *= len;
    		z *= len;
    	}
    }
    
    /**
     * Calculates the distance between the vector and another one.
     * @param other The vector to be used to calculate the distance.
     * @return A float with the total distance between the vectors.
     */
    public float distance(Vector3f other){
    	float dist = this.distanceSqr(other);
    	if(dist == 0.0f)
    		return 0.0f;
    	else
    		return (float)Math.sqrt(dist);
    }
    
    /**
     * Calculates the squares of the differences between the components of the vectors.
     * @param other The vector to be used to calculate the differences.
     * @return A float value with the squared differences of their components.
     */
    private float distanceSqr(Vector3f other){
    	float dx, dy, dz;
    	dx = x - other.getX();
    	dy = y - other.getY();
    	dz = z - other.getZ();
    	return (dx*dx) + (dy*dy) + (dz*dz);
    }


    /**
     *
     * @return The x component of the vector.
     */
    public float getX(){
        return x;
    }

    /**
     *
     * @return The y component of the vector.
     */
    public float getY(){
        return y;
    }

    /**
     *
     * @return The z component of the vector.
     */
    public float getZ(){
        return z;
    }
    
    /** 
     * Updates the x component of the vector.
     * @param x The value to set the x component.
     * @see #set(float, float,float)
     */
    public void setX(float x){
    	this.x=x;
    }
    
    /** 
     * Updates the y component of the vector.
     * @param y The value to set the y component.
     * @see #set(float, float,float)
     */
    public void setY(float y){
    	this.y=y;
    }
    
    /** 
     * Updates the z component of the vector.
     * @param z The value to set the z component.
     * @see #set(float, float,float)
     */
    public void setZ(float z){
    	this.z=z;
    }
    
    /** 
     * Updates both components of the vector.
     * @param x The value to set the x component.
     * @param y The value to set the y component.
     * @param z The value to set the z component.
     * @see #setX(float)
     * @see #setY(float)
     * @see #setZ(float)
     */
    public void set(float x, float y, float z){
    	this.x=x;
    	this.y=y;
    	this.z=z;
    }
    
    /**
     * Creates a string representation of the vector.
     * @return A string with the format: "(x,y,z)".
     */
    @Override
    public String toString(){
        return "(" + x + ", " + y + ", " + z + ")";
    }
    
    /**
     * Checks if the vector is equal to another one.<br /><br />
     * A vector is considered equal to another if the 
     * components in one have the same values as the components in the other one.
     * @param obj The vector to check equality with.
     * @return True if the vectors are equal, false otherwise.
     */
    @Override
    public boolean equals(Object obj){
    	if( obj instanceof Vector3f ){
    		Vector3f other = (Vector3f)obj;
    		
			return x == other.getX() &&
			       y == other.getY() &&
			       z == other.getZ();
		}else{
			return false;
		}
    }
}
